﻿/*************************************************************************
 * Created by OhZee Creative, LLC
 * 2013
 * This code is licensed under Microsoft Public License (Ms-PL)
 * http://opensource.org/licenses/ms-pl
 * http://ozcontrols.codeplex.com/
 *************************************************************************/

namespace OhZeeCreative.UI.Controls
{
    using System.ComponentModel;
    using System.Linq;
    using OhZeeCreative.Helpers;

#if SILVERLIGHT
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media.Animation;
    using System.Windows.Data;
#endif
#if NETFX_CORE
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Data;
    using Windows.UI.Xaml.Media.Animation;
#endif

    /// <summary>
    /// RadialRepeater is a control that clones the content specified in the ItemTemplate as many times as specified and lays it out radially. The clones can easily be rotated to point to the center. An example use is for creating tickmarks around a gauge.
    /// </summary>
    [EditorBrowsableAttribute(EditorBrowsableState.Always)]
    [TemplatePart(Name = RadialRepeater.TEMPLATE_PART_rlp, Type = typeof(RadialLayoutPanel))]
	public sealed class RadialRepeater : Control
	{
        /// <summary>
        /// The name of the RadialLayoutPanel element use
        /// </summary>
        public const string TEMPLATE_PART_rlp = "rlp";

		private RadialLayoutPanel radialLayoutPanel;
				
		// Relayed properties for RadialLayoutPanel
		public static readonly DependencyProperty RadiusBeginProperty = DependencyProperty.Register("RadiusBegin", typeof(double), typeof(RadialRepeater), new PropertyMetadata(200.0));
		public static readonly DependencyProperty RadiusEndProperty = DependencyProperty.Register("RadiusEnd", typeof(double), typeof(RadialRepeater), new PropertyMetadata(200.0));
		public static readonly DependencyProperty AngleBeginProperty = DependencyProperty.Register("AngleBegin", typeof(double), typeof(RadialRepeater), new PropertyMetadata(-135.0));
		public static readonly DependencyProperty AngleEndProperty = DependencyProperty.Register("AngleEnd", typeof(double), typeof(RadialRepeater), new PropertyMetadata(135.0));
		public static readonly DependencyProperty ChildAngleBeginProperty = DependencyProperty.Register("ChildAngleBegin", typeof(double), typeof(RadialRepeater), new PropertyMetadata(0.0));
		public static readonly DependencyProperty ChildAngleDeltaProperty = DependencyProperty.Register("ChildAngleDelta", typeof(double), typeof(RadialRepeater), new PropertyMetadata(0.0));
		public static readonly DependencyProperty EasingProperty = DependencyProperty.Register("Easing", typeof(EasingFunctionBase), typeof(RadialRepeater), new PropertyMetadata(null));
        public static readonly DependencyProperty ChildAngleEasingProperty = DependencyProperty.Register("ChildAngleEasing", typeof(EasingFunctionBase), typeof(RadialRepeater), new PropertyMetadata(null));
        public static readonly DependencyProperty ItemsSkippedProperty = DependencyProperty.Register("ItemsSkipped", typeof(int), typeof(RadialRepeater), new PropertyMetadata(0));
		public static readonly DependencyProperty ItemsMaximumProperty = DependencyProperty.Register("ItemsMaximum", typeof(int), typeof(RadialRepeater), new PropertyMetadata(int.MaxValue));
        public static readonly DependencyProperty OriginXProperty = DependencyProperty.Register("OriginX", typeof(double), typeof(RadialRepeater), new PropertyMetadata(0.0));
        public static readonly DependencyProperty OriginYProperty = DependencyProperty.Register("OriginY", typeof(double), typeof(RadialRepeater), new PropertyMetadata(0.0));
        public static readonly DependencyProperty CenterOriginProperty = DependencyProperty.Register("CenterOrigin", typeof(bool), typeof(RadialRepeater), new PropertyMetadata(true));

		// Own properties
		public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(int), typeof(RadialRepeater), new PropertyMetadata(10, ItemsPropertyChanged));
		public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(RadialRepeater), new PropertyMetadata(null, ItemTemplatePropertyChanged));

		private static void ItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			RadialRepeater repeater = (RadialRepeater)d;

			if (!DependencyHelper.CoerceMinimum(repeater, e, ItemsProperty, 0))
			{
				repeater.UpdateChildren();
			}
		}

		private static void ItemTemplatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			RadialRepeater repeater = (RadialRepeater)d;

			if (repeater.radialLayoutPanel != null)
			{
				repeater.radialLayoutPanel.Children.Clear();
				repeater.UpdateChildren();
			}
		}

#if SILVERLIGHT
        public override void OnApplyTemplate()
#endif
#if NETFX_CORE
		protected override void OnApplyTemplate()
#endif
		{
            this.radialLayoutPanel = this.GetTemplateChild(RadialRepeater.TEMPLATE_PART_rlp) as RadialLayoutPanel;

			Panel panel = this.radialLayoutPanel;   

			if (panel != null)
			{
				DependencyHelper.Bind(panel, RadialLayoutPanel.AngleBeginProperty, this, "AngleBegin", BindingMode.OneWay);
				DependencyHelper.Bind(panel, RadialLayoutPanel.AngleEndProperty, this, "AngleEnd", BindingMode.OneWay);
				DependencyHelper.Bind(panel, RadialLayoutPanel.ChildAngleBeginProperty, this, "ChildAngleBegin", BindingMode.OneWay);
				DependencyHelper.Bind(panel, RadialLayoutPanel.ChildAngleDeltaProperty, this, "ChildAngleDelta", BindingMode.OneWay);
				DependencyHelper.Bind(panel, RadialLayoutPanel.RadiusBeginProperty, this, "RadiusBegin", BindingMode.OneWay);
				DependencyHelper.Bind(panel, RadialLayoutPanel.RadiusEndProperty, this, "RadiusEnd", BindingMode.OneWay);
				DependencyHelper.Bind(panel, RadialLayoutPanel.EasingProperty, this, "Easing", BindingMode.OneWay);
                DependencyHelper.Bind(panel, RadialLayoutPanel.ChildAngleEasingProperty, this, "ChildAngleEasing", BindingMode.OneWay);
				DependencyHelper.Bind(panel, RadialLayoutPanel.ItemsSkippedProperty, this, "ItemsSkipped", BindingMode.OneWay);
				DependencyHelper.Bind(panel, RadialLayoutPanel.ItemsMaximumProperty, this, "ItemsMaximum", BindingMode.OneWay);
                DependencyHelper.Bind(panel, RadialLayoutPanel.OriginXProperty, this, "OriginX", BindingMode.OneWay);
                DependencyHelper.Bind(panel, RadialLayoutPanel.OriginYProperty, this, "OriginY", BindingMode.OneWay);
                DependencyHelper.Bind(panel, RadialLayoutPanel.CenterOriginProperty, this, "CenterOrigin", BindingMode.OneWay);

				this.UpdateChildren();
			}
		}
				
		#region Properties for dependency properties

        /// <summary>
        /// Gets or sets the length of the radius at the start angle, in pixels.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Always)]
		public double RadiusBegin
		{
			get { return (double)this.GetValue(RadiusBeginProperty); }
			set { this.SetValue(RadiusBeginProperty, value); }
		}

        /// <summary>
        /// Gets or sets the length of the radius at the end angle, in pixels.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Always)]
		public double RadiusEnd
		{
			get { return (double)this.GetValue(RadiusEndProperty); }
			set { this.SetValue(RadiusEndProperty, value); }
		}

        /// <summary>
        /// Gets or sets the starting angle in degrees according to Cartesian coordinates.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Always)]
		public double AngleBegin
		{
			get { return (double)this.GetValue(AngleBeginProperty); }
			set { this.SetValue(AngleBeginProperty, value); }
		}

        /// <summary>
        /// Gets or sets the ending angle in degrees according to Cartesian coordinates.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Always)]
		public double AngleEnd
		{
			get { return (double)this.GetValue(AngleEndProperty); }
			set { this.SetValue(AngleEndProperty, value); }
		}

        /// <summary>
        /// Gets or sets the rotation in degrees of the first item.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Always)]
		public double ChildAngleBegin
		{
			get { return (double)this.GetValue(ChildAngleBeginProperty); }
			set { this.SetValue(ChildAngleBeginProperty, value); }
		}

        /// <summary>
        /// Gets or sets the rotation in degrees of a child element relative to the previous one.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Always)]
		public double ChildAngleDelta
		{
			get { return (double)this.GetValue(ChildAngleDeltaProperty); }
			set { this.SetValue(ChildAngleDeltaProperty, value); }
		}

        /// <summary>
        /// Gets or sets the easing that determines how intermediate values of radius and angle are calculated between start and end.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
		public EasingFunctionBase Easing
		{
			get { return (EasingFunctionBase)this.GetValue(EasingProperty); }
			set { this.SetValue(EasingProperty, value); }
		}

        /// <summary>
        /// Gets or sets the easing that determines how intermediate values of radius and angle are calculated between start and end.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        public EasingFunctionBase ChildAngleEasing
        {
            get { return (EasingFunctionBase)this.GetValue(ChildAngleEasingProperty); }
            set { this.SetValue(ChildAngleEasingProperty, value); }
        }

        /// <summary>
        /// Gets or sets the number of initial items to omit from the layout.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
		public int ItemsSkipped
		{
			get { return (int)this.GetValue(ItemsSkippedProperty); }
			set { this.SetValue(ItemsSkippedProperty, value); }
		}

        /// <summary>
        /// Gets or sets the maximum number of items to include in the layout.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
		public int ItemsMaximum
		{
			get { return (int)this.GetValue(ItemsMaximumProperty); }
			set { this.SetValue(ItemsMaximumProperty, value); }
		}

        /// <summary>
        /// Gets the X origin relative to the left of the shape area.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public double OriginX
        {
            get { return (double)this.GetValue(OriginXProperty); }
            private set { this.SetValue(OriginXProperty, value); }
        }

        /// <summary>
        /// Gets the Y origin relative to the top of the shape area.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public double OriginY
        {
            get { return (double)this.GetValue(OriginYProperty); }
            private set { this.SetValue(OriginYProperty, value); }
        }

        /// <summary>
        /// Determines whether the origin should be centered within the width and height of the control.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Always)]
        public bool CenterOrigin
        {
            get { return (bool)this.GetValue(CenterOriginProperty); }
            set { this.SetValue(CenterOriginProperty, value); }
        }

        /// <summary>
        /// Gets or sets the number of items in the repeater.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Always)]
        public int Items
        {
            get { return (int)this.GetValue(ItemsProperty); }
            set { this.SetValue(ItemsProperty, value); }
        }
		
        /// <summary>
        /// Gets or sets the item template for the UI that will be cloned.
        /// </summary>
		public DataTemplate ItemTemplate
		{
			get { return (DataTemplate)this.GetValue(ItemTemplateProperty); }
			set { this.SetValue(ItemTemplateProperty, value); }
		}

		#endregion

		public RadialRepeater()
		{
			this.DefaultStyleKey = typeof(RadialRepeater);
		}
		
		private void UpdateChildren()
		{
			if (this.radialLayoutPanel == null || this.ItemTemplate == null)
			{
				return;
			}

			int newChildren  = this.Items;

			int oldChildren = this.radialLayoutPanel.Children.Count;

			for (int childIndex = oldChildren; childIndex < newChildren; childIndex++)
			{
				UIElement element = this.ItemTemplate.LoadContent() as UIElement;

				if (element == null)
				{
					break;
				}

				this.radialLayoutPanel.Children.Add(element);
			}

			for (int childIndex = newChildren; childIndex < oldChildren; childIndex++)
			{
				this.radialLayoutPanel.Children.Remove(this.radialLayoutPanel.Children.Last());
			}            
		}
	}
}
